#![allow(non_upper_case_globals, non_snake_case)]

use gl;
use glfw;

use gl::types::*;
use glfw::Context;

use gl_rs::utils::camera::Camera;
use gl_rs::utils::common::{load_texture, process_events, process_input};
use gl_rs::utils::shader::Shader;
use std::os::raw::c_void;
use std::{ffi::CStr, mem, ptr};

use cgmath::prelude::*;
use cgmath::{perspective, vec3, Deg, Matrix4, Point3, Vector3};

const SCREEN_WIDTH: u32 = 960;
const SCREEN_HEIGHT: u32 = 544;

pub fn main_2_5_1() {
    let mut camera = Camera {
        position: Point3::new(0.0, 0.0, 0.3),
        ..Camera::default()
    };

    let mut first = true;
    let mut last_x: f32 = SCREEN_WIDTH as f32 / 2.0;
    let mut last_y: f32 = SCREEN_HEIGHT as f32 / 2.0;
    let mut delta: f32;
    let mut last_frame: f32 = 0.0;

    let mut fw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
    fw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
    fw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));
    #[cfg(target_os = "macos")]
    fw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));

    let (mut window, events) = fw
        .create_window(SCREEN_WIDTH, SCREEN_HEIGHT, "LearnOpenGL", glfw::WindowMode::Windowed)
        .expect("Failed to create glfw window.");

    window.make_current();
    window.set_framebuffer_size_polling(true);
    window.set_cursor_pos_polling(true);
    window.set_scroll_polling(true);

    window.set_cursor_mode(glfw::CursorMode::Disabled);

    gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);
    let (lighting_shader, VBO, cubeVAO, lightVAO, diffuse_map, specular_map, cube_positions) = unsafe {
        gl::Enable(gl::DEPTH_TEST);
        let lighting_shader = Shader::new(
            "src/_2_lighting/shaders/5.1.light_casters.vert",
            "src/_2_lighting/shaders/5.1.light_casters.frag",
        );

        #[cfg_attr(rustfmt, rustfmt_skip)]
        let vertices: [f32; 288] = [
            -0.5, -0.5, -0.5,  0.0,  0.0, -1.0, 0.0, 0.0,
             0.5, -0.5, -0.5,  0.0,  0.0, -1.0, 1.0, 0.0,
             0.5,  0.5, -0.5,  0.0,  0.0, -1.0, 1.0, 1.0,
             0.5,  0.5, -0.5,  0.0,  0.0, -1.0, 1.0, 1.0,
            -0.5,  0.5, -0.5,  0.0,  0.0, -1.0, 0.0, 1.0,
            -0.5, -0.5, -0.5,  0.0,  0.0, -1.0, 0.0, 0.0,

            -0.5, -0.5,  0.5,  0.0,  0.0, 1.0, 0.0, 0.0,
             0.5, -0.5,  0.5,  0.0,  0.0, 1.0, 1.0, 0.0,
             0.5,  0.5,  0.5,  0.0,  0.0, 1.0, 1.0, 1.0,
             0.5,  0.5,  0.5,  0.0,  0.0, 1.0, 1.0, 1.0,
            -0.5,  0.5,  0.5,  0.0,  0.0, 1.0, 0.0, 1.0,
            -0.5, -0.5,  0.5,  0.0,  0.0, 1.0, 0.0, 0.0,

            -0.5,  0.5,  0.5, -1.0,  0.0, 0.0, 1.0, 0.0,
            -0.5,  0.5, -0.5, -1.0,  0.0, 0.0, 1.0, 1.0,
            -0.5, -0.5, -0.5, -1.0,  0.0, 0.0, 0.0, 1.0,
            -0.5, -0.5, -0.5, -1.0,  0.0, 0.0, 0.0, 1.0,
            -0.5, -0.5,  0.5, -1.0,  0.0, 0.0, 0.0, 0.0,
            -0.5,  0.5,  0.5, -1.0,  0.0, 0.0, 1.0, 0.0,

             0.5,  0.5,  0.5,  1.0,  0.0, 0.0, 1.0, 0.0,
             0.5,  0.5, -0.5,  1.0,  0.0, 0.0, 1.0, 1.0,
             0.5, -0.5, -0.5,  1.0,  0.0, 0.0, 0.0, 1.0,
             0.5, -0.5, -0.5,  1.0,  0.0, 0.0, 0.0, 1.0,
             0.5, -0.5,  0.5,  1.0,  0.0, 0.0, 0.0, 0.0,
             0.5,  0.5,  0.5,  1.0,  0.0, 0.0, 1.0, 0.0,

            -0.5, -0.5, -0.5,  0.0, -1.0, 0.0, 0.0, 1.0,
             0.5, -0.5, -0.5,  0.0, -1.0, 0.0, 1.0, 1.0,
             0.5, -0.5,  0.5,  0.0, -1.0, 0.0, 1.0, 0.0,
             0.5, -0.5,  0.5,  0.0, -1.0, 0.0, 1.0, 0.0,
            -0.5, -0.5,  0.5,  0.0, -1.0, 0.0, 0.0, 0.0,
            -0.5, -0.5, -0.5,  0.0, -1.0, 0.0, 0.0, 1.0,

            -0.5,  0.5, -0.5,  0.0,  1.0, 0.0, 0.0, 1.0,
             0.5,  0.5, -0.5,  0.0,  1.0, 0.0, 1.0, 1.0,
             0.5,  0.5,  0.5,  0.0,  1.0, 0.0, 1.0, 0.0,
             0.5,  0.5,  0.5,  0.0,  1.0, 0.0, 1.0, 0.0,
            -0.5,  0.5,  0.5,  0.0,  1.0, 0.0, 0.0, 0.0,
            -0.5,  0.5, -0.5,  0.0,  1.0, 0.0, 0.0, 1.0
        ];

        #[cfg_attr(rustfmt, rustfmt_skip)]
        let cube_positions: [Vector3<f32>; 10] = [
            vec3( 0.0,  0.0,  0.0),
            vec3( 2.0,  5.0, -15.0),
            vec3(-1.5, -2.2, -2.5),
            vec3(-3.8, -2.0, -12.3),
            vec3( 2.4, -0.4, -3.5),
            vec3(-1.7,  3.0, -7.5),
            vec3( 1.3, -2.0, -2.5),
            vec3( 1.5,  2.0, -2.5),
            vec3( 1.5,  0.2, -1.5),
            vec3(-1.3,  1.0, -1.5)
        ];
        let (mut VBO, mut cubeVAO) = (0, 0);
        gl::GenVertexArrays(1, &mut cubeVAO);
        gl::GenBuffers(1, &mut VBO);

        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
        gl::BufferData(
            gl::ARRAY_BUFFER,
            (vertices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &vertices[0] as *const f32 as *const c_void,
            gl::STATIC_DRAW,
        );
        gl::BindVertexArray(cubeVAO);
        let stride = 8 * mem::size_of::<GLfloat>() as GLsizei;

        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null());
        gl::EnableVertexAttribArray(0);

        gl::VertexAttribPointer(
            1,
            3,
            gl::FLOAT,
            gl::FALSE,
            stride,
            (3 * mem::size_of::<GLfloat>()) as *const c_void,
        );
        gl::EnableVertexAttribArray(1);

        gl::VertexAttribPointer(
            2,
            2,
            gl::FLOAT,
            gl::FALSE,
            stride,
            (6 * mem::size_of::<GLfloat>()) as *const c_void,
        );
        gl::EnableVertexAttribArray(2);

        let mut lightVAO = 0;
        gl::GenVertexArrays(1, &mut lightVAO);
        gl::BindVertexArray(lightVAO);

        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);

        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null());
        gl::EnableVertexAttribArray(0);

        let diffuse_map = load_texture("resources/textures/container2.png");
        let specular_map = load_texture("resources/textures/container2-specular.png");

        lighting_shader.use_program();
        lighting_shader.set_int(c_str!("material.diffuse"), 0);
        lighting_shader.set_int(c_str!("material.specular"), 1);

        (
            lighting_shader,
            VBO,
            cubeVAO,
            lightVAO,
            diffuse_map,
            specular_map,
            cube_positions,
        )
    };

    while !window.should_close() {
        let current_frame = fw.get_time() as f32;
        delta = current_frame - last_frame;
        last_frame = current_frame;

        process_events(&events, &mut first, &mut last_x, &mut last_y, &mut camera);
        process_input(&mut window, delta, &mut camera);

        unsafe {
            gl::ClearColor(0.1, 0.1, 0.1, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
            lighting_shader.use_program();
            lighting_shader.set_vec3(c_str!("light.direction"), -0.2, -1.0, -0.3);
            lighting_shader.set_vector3(c_str!("viewPos"), &camera.position.to_vec());

            lighting_shader.set_vec3(c_str!("light.ambient"), 0.2, 0.2, 0.2);
            lighting_shader.set_vec3(c_str!("light.specular"), 1.0, 1.0, 1.0);

            lighting_shader.set_float(c_str!("material.shininess"), 32.0);

            let projection: Matrix4<f32> =
                perspective(Deg(camera.zoom), SCREEN_WIDTH as f32 / SCREEN_HEIGHT as f32, 0.1, 100.0);
            let view = camera.get_view_matrix();
            lighting_shader.set_mat4(c_str!("projection"), &projection);
            lighting_shader.set_mat4(c_str!("view"), &view);
            let model = Matrix4::<f32>::identity();
            lighting_shader.set_mat4(c_str!("model"), &model);
            gl::ActiveTexture(gl::TEXTURE0);
            gl::BindTexture(gl::TEXTURE_2D, diffuse_map);
            gl::ActiveTexture(gl::TEXTURE1);
            gl::BindTexture(gl::TEXTURE_2D, specular_map);

            gl::BindVertexArray(cubeVAO);

            for (i, position) in cube_positions.iter().enumerate() {
                let mut model: Matrix4<f32> = Matrix4::from_translation(*position);
                let angle = 20.0 * i as f32;
                model = model * Matrix4::from_axis_angle(vec3(1.0, 0.3, 0.5).normalize(), Deg(angle));
                lighting_shader.set_mat4(c_str!("model"), &model);
                gl::DrawArrays(gl::TRIANGLES, 0, 36);
            }
        }
        window.swap_buffers();
        fw.poll_events();
    }
    unsafe {
        gl::DeleteVertexArrays(1, &cubeVAO);
        gl::DeleteVertexArrays(1, &lightVAO);
        gl::DeleteBuffers(1, &VBO);
    }
}
